<?php
/**
 * Parcl
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://parcl.com/license
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@parcl.com so we can send you a copy immediately.
 *
 * @category parcl
 * @package data-store
 * @subpackage database
 * @copyright Copyright (c) 2006-2011 Mikael Knutsson (http://mikn.me)
 * @license http://parcl.com/license New BSD License
 *
 * @todo Might make it into a parcel together with the sessions? Thing is,
 * it should probably be there because you should be able to change it.
 */

namespace Parcl\Core {

	if(PARCL_EXEC !== 1) {
		die('<h1>Incorrect Access</h1>You cannot access this file directly.<br/>' .
			 'If you got this error message by clicking a link on the site,' .
			 'please notify the webmaster as soon as possible.');
	}

	/**
	 * Purpose     : Defining the database connection and communication.
	 * Description : This class defines the database connection and most things surrounding it.
	 * - Construct -
	 * Input       : None.
	 * Output      : Not applicable.
	 * Description : Brings in the necessary variables to class variables.
	 * Errors      : None.
	 * Assumptions : That the Registry class is available.
	 * Dev Notes   : None at the moment.
	 */

	class parclDB extends Events {
		const BLKSIZE = 4096;

		private $connected = false;
		private $PDOdb = null;
		private static $queryCount = 0;
		private static $queryTime = array();
		private $result = array();
		private $stmt = null;
		private $queryPipelined = false;

		public function __construct() {
			parent::__construct();
			$this->registeredEvents->AddDbEvents($this->events);
		}

		public function __clone() {
			throw new GeneralException('The DB object must not be cloned.',
								   GeneralException::CLONING);
		}

		private function connect() {
			$registry = Registry::GetInstance();
			$INFO = $registry->conf;
			$dsn = "$INFO->dbType:dbname=$INFO->dbName;host=$INFO->dbHost";
			$PDOdb = new PDO($dsn, $INFO->dbUser, $INFO->dbPass);
			$PDOdb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

			$PDOdb->exec("SET NAMES 'UTF8'");
			$this->PDOdb = $PDOdb;
			$this->connected = true;
			$this->triggerEvent('DB:CONNECTED');
		}

		private function connected() {
			watchdog::Log('Requesting database connection.', LogLevel::INFORMATIONAL);
			if($this->connected) {
				return true;
			}
			else {
				$this->connect();
				return true;
			}
		}

		public function Query(Query $query, $check = true) {
			$finalQuery = $query->GetQuery();
			$this->triggerEvent('DB:QUERY_RECEIVED', array('Query' => &$finalQuery, 'check' => $check));
			$this->connected();
			$this->PDOdb->beginTransaction();
			$this->queryPipelined = true;
			$this->prepareQuery($finalQuery);
		}

		private function prepareQuery($query) {
			$PDOdb = $this->PDOdb;

			if(mb_strpos($query, '?') === false) {
				throw new DBException('Only accepting prepared statements.', DBException::QUERY);
			}

			$stmt = $PDOdb->prepare($query);

			$this->stmt = $stmt;
			$this->triggerEvent('DB:QUERY_PREPARED');
		}

		public function Execute(parclDB $sender, $args) {
			$stmt = $args['stmt'];
			$stmt->execute();
			$this->PDOdb->commit();
			$this->queryPipelined = false;
			$this->triggerEvent('DB:EXECUTION_MADE');
			self::$queryCount++;
		}

		public function CloseCursor(parclDB $sender, $args) {
			$stmt = $args['stmt'];
			$stmt->closeCursor();
		}

		public function BindParams(Query $query, $args) {
			$params = $args['params'];
			if($query->MarkerCount() !== count($params)) {
				throw new DBException('Number of parameters does not match number of
									   variables passed.<br />Number of parameters: ' .
									   $query->MarkerCount() . '<br/>Number of variables: ' .
									   count($params) , DBException::PARAM);
			}

			$stmt = $this->stmt;
			foreach($params as $k => $param) {
				$stmt->bindParam($k + 1, $param['value'], $param['type']);
			}
			$this->triggerEvent('DB:PARAMS_BOUND', array('stmt' => $stmt));
		}

		public function FetchResult($sender, $args) {
			$stmt = $this->stmt;
			$args['output'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
			$this->triggerEvent('DB:RESULT_GENERATED', array('stmt' => $stmt));
		}

		public static function getQueryCount() {
			return self::$queryCount;
		}

		public static function getQueryTime() {
			return array_sum(self::$queryTime);
		}

		public function numQueryRows() {
			return $this->numQueryRows;
		}

		public function Close() {
			if($this->queryPipelined) {
				$this->PDOdb->rollBack();
			}
			unset($this->PDOdb);
		}

		public function qStartTimer($sender, $args) {
			self::$queryTime[self::$queryCount] = Timers::StartTimer();
		}
		public function qEndTimer($sender, $args) {
			self::$queryTime[self::$queryCount] = Timers::StopTimer(self::$queryTime[self::$queryCount]);
		}
	}
}